#include "Triangle.hpp"
#include "rasterizer.hpp"
#include <eigen3/Eigen/Eigen>
#include <iostream>
#include <opencv2/opencv.hpp>

constexpr double MY_PI = 3.1415926;

Eigen::Matrix4f get_view_matrix(Eigen::Vector3f eye_pos)
{
    Eigen::Matrix4f view = Eigen::Matrix4f::Identity();

    Eigen::Matrix4f translate;
    translate << 
             1, 0, 0, -eye_pos[0], 
             0, 1, 0, -eye_pos[1], 
             0, 0, 1, -eye_pos[2], 
             0, 0, 0, 1;

    view = translate * view;

    return view;
}

Eigen::Matrix4f get_model_matrix(float rotation_angle)
{
    Eigen::Matrix4f model = Eigen::Matrix4f::Identity();

    // TODO: Implement this function
    // Create the model matrix for rotating the triangle around the Z axis.
    // Then return it.

    Eigen::Vector3f axis(0,0,1);
    float rad_angle = rotation_angle / 180 * MY_PI;

    auto u = axis.normalized();
    
    Eigen::Matrix3f u_mul;
    
    u_mul << 
        0.0f, u[2],-u[1],
        -u[2], 0.0f,u[0],
        u[1],-u[0],0.0f;

    Matrix3f rot = Eigen::Matrix3f::Identity() * std::cos(rad_angle) ;
    rot  +=  (1 - std::cos(rad_angle)) * u * u.transpose();
    rot +=  std::sin(rad_angle) * u_mul;


    for (int i = 0;i < 3; ++i) 
        for (int j = 0;j < 3; ++j)
            model(i,j) = rot(i,j);

    return model;
}

Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio,
                                      float zNear, float zFar)
{
    // Students will implement this function

    Eigen::Matrix4f projection = Eigen::Matrix4f::Identity();

    // TODO: Implement this function
    // Create the projection matrix for the given parameters.
    // Then return it.


    // fov = tan(theta/2) = w/2 * zNear

    float w = 2 * std::abs(zNear) * std::tan(0.5 * eye_fov);
    float h = w / aspect_ratio;

    projection(0,0) = 2 * zNear / w;
    projection(1,1) = 2 * zNear / h;
    projection(2,2) = (zFar + zNear)  / (zNear - zFar);
    projection(3,2) = 1; 
    projection(2,3) = 2 * zFar * zNear / zFar - zNear;
    projection(3,3) = 0;

    return projection;
}
bool quit_button_pressed(int key) 
{
    
   switch ( key)
   {
    /* ESC pressed*/
   case 27:
    return true;
    break;
   case 'q':
   return true;
    break;
   default:
    break;
   }
    return false;
}
int main(int argc, const char** argv)
{
    float angle = 0;
    bool command_line = false;
    std::string filename = "output.jpeg";

    if (argc >= 3) {
        command_line = true;
        angle = std::stof(argv[2]); // -r by default
        if (argc == 4) {
            filename = std::string(argv[3]);
        }
        else
            return 0;
    }

    rst::rasterizer r(700, 700);

    Eigen::Vector3f eye_pos = {0, 0, 5};

    std::vector<Eigen::Vector3f> pos{{2, 0, -2}, {0, 2, -2}, {-2, 0, -2}};

    std::vector<Eigen::Vector3i> ind{{0, 1, 2}};

    auto pos_id = r.load_positions(pos);
    auto ind_id = r.load_indices(ind);

    int key = 0;
    int frame_count = 0;

    if (command_line) {
        r.clear(rst::Buffers::Color | rst::Buffers::Depth);

        r.set_model(get_model_matrix(angle));
        r.set_view(get_view_matrix(eye_pos));
        r.set_projection(get_projection_matrix(45, 1, 0.1, 50));

        r.draw(pos_id, ind_id, rst::Primitive::Triangle);
        cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data());
        image.convertTo(image, CV_8UC3, 1.0f);

        cv::imwrite(filename, image);

        return 0;
    }

    while ( !quit_button_pressed( key) ) {
        r.clear(rst::Buffers::Color | rst::Buffers::Depth);

        r.set_model(get_model_matrix(angle));
        r.set_view(get_view_matrix(eye_pos));
        r.set_projection(get_projection_matrix(45, 1, 0.1, 50));

        r.draw(pos_id, ind_id, rst::Primitive::Triangle);

        cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data());
        image.convertTo(image, CV_8UC3, 1.0f);
        cv::imshow("image", image);
        key = cv::waitKey(10);

        std::cout << "frame count: " << frame_count++ << '\n';

        if (key == 'a' )  {
            angle += 10;
        }
        else if (key == 'd' )  {
            angle -= 10;
        }
    }

    return 0;
}
